package manger;

import dao.FileDao;
import dao.FileMeta;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

//针对目录进行扫描,并把扫描的结果同步更新到数据库中
public class FileManger {
    private FileDao fileDao = new FileDao();

    //为了使得计时是准确的,引入CountDownLatch
    //初始化设置选手数目为1,当线程池执行完所有任务之后,就调用一次countDown撞线.
    private CountDownLatch countDownLatch = null;
    //用来衡量任务结束的计数器
    //AtomicInteger是线程安全的
    private AtomicInteger taskCount = new AtomicInteger(0);
    //通过这个方法,实现针对basePath描述的目录的内容的扫描
    //把这里的文件和子目录都扫描清楚,并且保存到数据库中
    public void scanAll(File basePath){
        System.out.println("[FileManger] scanAll 开始!");
        long begin = System.currentTimeMillis();
        //保证每次调用scanAll都重新创建countDownLatch,重新进行计数了
        countDownLatch = new CountDownLatch(1);
        //scanAllByOneThread(basePath);
        scanAllByThreadPool(basePath);
        try {
            //让主线程等待
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("[FileManger] scanAll结束! 执行时间: " + (end-begin)+"ms");
    }

    //单线程的扫描
    private void scanAllByOneThread(File basePath){
        if (!basePath.isDirectory()){
            return;
        }
        //现针对当前路径进行扫描
        scan(basePath);
        //列出当前目录下的包含的所有文件
        File[] files = basePath.listFiles();
        if (files==null || files.length==0){
            //当前目录下没东西
            return;
        }
        for (File f:files){
            if (f.isDirectory()){
                scanAllByOneThread(f);
            }
        }
    }
    //多线程扫描
    //创建一个线程池
    private static ExecutorService executorService = Executors.newFixedThreadPool(8);
    private void scanAllByThreadPool(File basePath){
        if (!basePath.isDirectory()){
            return;
        }
        //计数器自增
        taskCount.getAndIncrement();
        //扫描操作放到线程池里完成.
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    scan(basePath);
                } finally {
                    //计数器自减
                    //把自减操作放到finally里
                    //确保自减操作肯定是能执行到的.
                    taskCount.getAndDecrement();
                    if (taskCount.get()==0){
                        //如果计数器为0,通知主线程停表
                        countDownLatch.countDown();
                    }
                }
            }
        });
        //继续递归其他目录
        File[] files = basePath.listFiles();
        if (files==null || files.length==0){
            return;
        }
        for (File f:files) {
            if (f.isDirectory()){
                scanAllByThreadPool(f);
            }
        }
    }


    //scan方法针对一个目录进行处理(整个遍历目录过程中的基本操作)
    //这个方法只针对当前path对应的目录进行分析
    //列出这个path下包含的文件和子目录,并且把这些内容更新到数据库中
    //此方法不考虑子目录里的内容
    private void scan(File path){
        System.out.println("[FileManger] 扫描路径: " + path.getAbsolutePath());
        //1.列出文件系统上真实的文件或者目录
        List<FileMeta> scanned = new ArrayList<>();
        File[] files = path.listFiles();
        if (files != null){
            for (File f:files) {
                scanned.add(new FileMeta(f));
            }
        }

        //2.列出数据库中当前指定目录里的内容
        List<FileMeta> saved = fileDao.searchByPath(path.getPath());

        //3.找出文件系统中没有的,数据库中有的,把这些内容删除掉
        List<FileMeta> forDelete = new ArrayList<>();//存储要删除的内容
        for (FileMeta fileMeta:saved) {
            if (!scanned.contains(fileMeta)){
                forDelete.add(fileMeta);
            }
        }
        fileDao.delete(forDelete);

        //4.找出文件系统有的,数据库中没有的,往数据库中插入
        List<FileMeta> forAdd = new ArrayList<>();
        for (FileMeta fileMeta:scanned) {
            if (!saved.contains(fileMeta)){
                //找出文件系统中有的,数据库中没有的
                forAdd.add(fileMeta);
            }
        }
        fileDao.add(forAdd);
    }
}
